package hu.bme.iit.szlab4.continuity.proto;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class Control {
	// a jatekot iranyitjuk-e vagy a programot
	private static boolean kulso = true;
	// a palya obejktum
	private static Palya palya;
	// referencia a jatekosokra (a statuszkiiras miatt kell)
	private static List<Jatekos> jatekoslista;
	// referencia a tileokra (a statuszkiiras miatt kell)
	private static List<Tile> tilelista;
	// a parancsokat tartalmazo lista
	private static Queue<Command> q = new LinkedList<Command>();
	// kimenet: fajlba
	private static boolean fajlba = false;
	// kimenet: kepernyore
	private static boolean kepenyore = true;
	// output fajl neve
	private static String fajlnev;
	
	//belepesi pont
	public static void main(String[] args) {
		Command c = new Command();
		
		// CommandList inicializalasa, ha meg nem tortent meg
		if(!CommandList.isInitialized()){
			CommandList.initialize();
		}
		
		try {
			c = parse("help");
			vegrehajt(c);
		} catch (Exception e) {
			kiir("HIBA: "+e.getMessage());
		}
		
		// olvassuk a bemenetet mig quit parancsot nem kapunk
		String s = null;
		do{
			s = null;
			//c = new Command();
			try {
				InputStreamReader isr = new InputStreamReader(System.in);
				BufferedReader br = new BufferedReader(isr);
				s = br.readLine();
				c = parse(s);
				vegrehajt(c);
			} catch (Exception e) {
				kiir("HIBA: "+e.getMessage());
			}
			if(s==null){
				continue;
			}
			if (palya!=null) {
				// ha vege a palyanak
				if (palya.getVege()) {
					kiir("Vege a jateknak!");
					kiir("A reset paranccsal ujrakezdheted az aktualis tesztesetet, vagy az exit paranccsal visszalephetsz a fomenube!");
				}
			}
		}while (s==null || c.getCommand()!=CommandList.Commands.quit);
			
	}
	
	public static Command parse(String s) throws Exception{
		// a leendo visszateresi ertek
		Command com = new Command();
		
		// kezdo es zaro whitespace-ek eltavolitasa
		s=s.trim();
		
		// benti whitespace-ek lecserelese egy space-re
		s=s.replaceAll("[ \t]", " ");
		
		// a beolvasott parancs szokozoknel szetvagott stringjeit
		// tartalmazo tomb
		String[] splitted=s.split(" +");
		
		// vizsgaljuk, hogy ervenyes-e a parancs
		if (splitted.length>0 && splitted.length<=2) {
			CommandList.Commands c;
			try {
				// help lekezelese (kulso/belso)
				if(splitted[0].equals("help")){
					if(kulso){
						c = CommandList.Commands.helpkulso;
					}
					else{
						c = CommandList.Commands.helpbelso;
					}
				}
				else{
					// ha letezik a parancs, atalakitja enumma,
					// ha nem, IllegalArgumentExceptiont dob
					c = CommandList.Commands.valueOf(splitted[0]);
				}
			} catch (Exception e) {
				// az exceptiont atalakitjuk ember altal ertelmezhetove
				throw new Exception("Hibas parancs! Segitseghez hasznalja a help parancsot.");
			}
			
			CommandElement tmpce;
			tmpce = CommandList.getCommandElement(c);
			
			// beallitjuk a commandelementet
			com.setCommandElement(tmpce);
			// ha nem parameteres a parancs, a string tovabbi reszet figyelmen kivul hagyjuk
			if(tmpce.getParameterSzam()==0){
				com.setNum(null);
				com.setParam(null);
			}
			
			// ha parameteres, vizsgaljuk, hogy helyes-e a megadott parameter
			else{
				if(splitted.length==2){
					
					// a tick es a tesctcase int-et var
					if((c==CommandList.Commands.tick)||(c==CommandList.Commands.testcase)){
						try {
							// int beallitasa
							com.setNum(Integer.parseInt(splitted[1]));
							// masik parameter kinullazasa
							com.setParam(null);
						} catch (Exception e) {
							throw new Exception("A " + c +" lehetseges parameterei: egesz szam.");
						}
					}
					
					// a movej1 es movej2 lehetseges parameterei:
					// up | left | right
					else if((c==CommandList.Commands.movej1)||(c==CommandList.Commands.movej2)){
						
						// letezik-e az a jatekos, amit mozgatni szeretnenk?
						if(jatekoslista==null){
							throw new Exception("Nincs jatekos inicializalva!");
						}
						if(c==CommandList.Commands.movej1 && jatekoslista.size()<1){
							throw new Exception("Hibas parancs \"movej1\"! Nem letezik az 1. jatekos!");
						}
						if(c==CommandList.Commands.movej2 && jatekoslista.size()<2){
							throw new Exception("Hibas parancs \"movej2\"! Nem letezik a 2. jatekos!");
						}
						
						// reszletes nezetben van-e
						if(c==CommandList.Commands.movej1 && !jatekoslista.get(0).getReszletes()){
							throw new Exception("Hibas parancs \"movej1\"! Valts reszletes nezetbe (switchview j1)!");
						}
						if(c==CommandList.Commands.movej2 && !jatekoslista.get(1).getReszletes()){
							throw new Exception("Hibas parancs \"movej2\"! Valts reszletes nezetbe (switchview j2)!");
						}
						
						// ha lezetik, megnezzuk a parametereket
						if(splitted[1].equals("up")||
								splitted[1].equals("left")||splitted[1].equals("right")){
							com.setParam(splitted[1]);
							com.setNum(null);
						}
						else{
							throw new Exception("A " + c + " lehetseges parameterei: up, left, right.");
						}
					}
					
					// a movetile-ok lehetseges parameterei:
					// up | down | left | right
					else if(c==CommandList.Commands.movetilej1 ||
							c==CommandList.Commands.movetilej2){
						if(splitted[1].equals("up")||splitted[1].equals("down")||
								splitted[1].equals("left")||splitted[1].equals("right")){
							com.setParam(splitted[1]);
							com.setNum(null);
						}
						else{
							throw new Exception("A " + c + " lehetseges" +
									"parameterei: up, down, left, right.");
						}
					}

					// az output lehetseges parameterei:
					// scr | file | both
					else if(c==CommandList.Commands.output){
						if(splitted[1].equals("scr")||splitted[1].equals("file")||
								splitted[1].equals("both")){
							com.setParam(splitted[1]);
							com.setNum(null);
						}
						else{
							throw new Exception("A " + c + " lehetseges" +
									"parameterei: scr, file, both.");
						}
					}
					
					// a switchview lehetseges parameterei:
					// j1 | j2
					else if(c==CommandList.Commands.switchview){
						// letezik-e az a jatekos, aminek a nezetet valtoztatni akarjuk
						if(jatekoslista==null){
							throw new Exception("Nincs jatekos inicializalva!");
						}
						if(splitted[1].equals("j1") && jatekoslista.size()<1){
							throw new Exception("Hibas parancs \"switchview j1\"! Nem letezik az 1. jatekos!");
						}
						if(splitted[1].equals("j2") && jatekoslista.size()<2){
							throw new Exception("Hibas parancs \"switchview j2\"! Nem letezik a 2. jatekos!");
						}
						
						// ha letezik a jatekos, mar csak a parameterek helyesseget ellenorizzuk
						if(splitted[1].equals("j1")||splitted[1].equals("j2")){
							com.setParam(splitted[1]);
							com.setNum(null);
						}
						else{
							throw new Exception("A " + c + " lehetseges" +
									"parameterei: j1, j2.");
						}
					}
					
					// a parancsokat tartalmazo fajl nevet varja
					else if(c==CommandList.Commands.commandfile){
						com.setParam(splitted[1]);
						com.setNum(null);
					}
					
				}
				else{
					throw new Exception("Nem megfelelo szamu parameter! Segitseghez hasznalja a help parancsot.");
				}
			}
		} else {
			throw new Exception("Nem megfelelo parancs! Segitseghez hasznalja a help parancsot.");
		}
		// ha a parancs nem az aktualis allapotra vonatkozik
		if(!com.getCommand().equals(CommandList.Commands.reset)){
			if(com.getCommandElement().isBelso()==kulso){
				throw new Exception("Nem megfelelo kontextus! Segitseghez hasznalja a help parancsot.");
			}
		}
		return com;
	}
	
	private static void futtat(Command com) throws Exception{
		CommandList.Commands c = com.getCommand();
		/*
		 * XNOR
		 * true, ha mindketto igaz, vagy mindketto hamis
		 * false egyebkent
		 */
		if( kulso == !(com.getCommandElement().isBelso()) ){
			switch (c) {
			case testcase:
				// palya letrehozasa es inicializalasa
				palya = new Palya();
				palya.init(com.getNum());
				// tile-ok referenciainak lekerese
				tilelista = palya.getTile();
				// jatekosok referenciainak lekerese
				jatekoslista = palya.getJatekos();
				kulso = false;
				tileAllapot = new ArrayList<ArrayList<String>>();
				j1Allapot = null;
				j2Allapot = null;
				allapotKiir(null);
				break;
			// belso help (!kulso eseten)
			case helpbelso:
				kiir("  ervenyes parancsok:");
				kiir("\texit - Kilepes a menube");
				kiir("\thelp - Ez a help");
				kiir("\tmovej1* [up | left | right] - Az 1-es jatekos mozgatasa a megfelelo iranyba (reszletes nezetben)");
				kiir("\tmovej2* [up | left | right] - A 2-es jatekos mozgatasa a megfelelo iranyba (reszletes nezetben)");
				kiir("\tmovetile* [up | down | left | right] - tile-ok mozgatasa (attekinto nezetben)");
				kiir("\treset - az aktualis teszteset alaphelyzetbe valo allitasa");
				kiir("\tswitchview* [j1 | j2] - nezetvaltas (attekinto/reszletes)");	
				kiir("\ttick [n] - az elozo n utasitas vegrehajtasa");
				kiir("\n\t* - az utasitas csak tick hatasara fut le");
				kiir("");
				break;
			// kulso help (kulso eseten)
			case helpkulso:
				kiir("ervenyes parancsok:");
				kiir("\tcommandfile [fajlnev] - a fajlnev nevu fajlban talalhato parancsszekvencia lefuttatasa");
				kiir("\thelp - Ez a help");
				kiir("\toutput [fajlnev] - A kimenet fajlba irasa");
				kiir("\tquit - Kilepes a programbol");
				kiir("\ttestcase [n] - az n-edik teszteset betoltese es valtas a jatek iranyitasara");
				kiir("");
				break;
			case commandfile:
				try{
					// fajl megnyitasa
					FileInputStream fstream = new FileInputStream(com.getParam());
					DataInputStream in = new DataInputStream(fstream);
					BufferedReader br = new BufferedReader(new InputStreamReader(in));
					String strLine;
					// beolvasas sorrol sorra
					while ((strLine = br.readLine()) != null)	 {
						// aktualis sor vegrehajtasa
						kiir(strLine);
						Command commandFromFile = parse(strLine);
						vegrehajt(commandFromFile);
					}
					//Close the input stream
					in.close();
				}catch (Exception e){//Catch exception if any
					throw new Exception("Olvasasi hiba! " + e.getMessage());
				}
				break;
			case movej1:
				Billentyu bill;
				if(com.getParam().equals("up")){
					// a j1.fel beallitasa true-ba
					bill = new Billentyu(false, false, true, false, false, false, false, false, false, false);
					palya.mozgatUgras(bill);
					break;
				}
				else{
					if(com.getParam().equals("left")){
						// j1.bal beallitasa true-ba
						bill = new Billentyu(false, true, false, false, false, false, false, false, false, false);
					}
					// ez mar csak a right lehet, parse fv-ben tesztelve
					else{
						// j1.jobb beallitasa true-ba
						bill = new Billentyu(true, false, false, false, false, false, false, false, false, false);
					}
					palya.mozgatLep(bill);
				}
				break;
			case movej2:
				// a movej1-nel deklaralt bill-t hasznaljuk
				if(com.getParam().equals("up")){
					// a j2.fel beallitasa true-ba
					bill = new Billentyu(false, false, false, false, false, false, false, true, false, false);
					palya.mozgatUgras(bill);
					break;
				}
				else{
					if(com.getParam().equals("left")){
						// j2.bal beallitasa true-ba
						bill = new Billentyu(false, false, false, false, false, false, true, false, false, false);
					}
					// ez mar csak a right lehet, parse fv-ben tesztelve
					else{
						// j2.jobb beallitasa true-ba
						bill = new Billentyu(false, false, false, false, false, true, false, false, false, false);
					}
					palya.mozgatLep(bill);
				}
				break;
			case movetilej1:
				// csak ha attekinto nezetben vagyunk, egyebkent hiba
				if(!jatekoslista.get(0).getReszletes()){
					if(com.getParam().equals("up")){
						bill = new Billentyu(false, false, true, false, false, false, false, false, false, false);
					}
					else if(com.getParam().equals("down")){
						bill = new Billentyu(false, false, false, true, false, false, false, false, false, false);
					}
					else if(com.getParam().equals("left")){
						bill = new Billentyu(false, true, false, false, false, false, false, false, false, false);
					}
					// csak right lehet, parse fv-ben tesztelve
					else{
						bill = new Billentyu(true, false, false, false, false, false, false, false, false, false);
					}
					palya.attekintoMozgat(bill);
				}
				else{
					throw new Exception("Hibas parancs! Segitseghez hasznalja a help parancsot.");
				}
				break;
			case movetilej2:
				if(!jatekoslista.get(1).getReszletes()){
					if(com.getParam().equals("up")){
						bill = new Billentyu(false, false, false, false, false, false, false, true, false, false);
					}
					else if(com.getParam().equals("down")){
						bill = new Billentyu(false, false, false, false, false, false, false, false, true, false);
					}
					else if(com.getParam().equals("left")){
						bill = new Billentyu(false, false, false, false, false, false, true, false, false, false);
					}
					// csak right lehet, parse fv-ben tesztelve
					else{
						bill = new Billentyu(false, false, false, false, false, true, false, false, false, false);
					}
					palya.attekintoMozgat(bill);
				}
				else{
					throw new Exception("Hibas parancs! Segitseghez hasznalja a help parancsot.");
				}
				break;
			case reset:
				// palya letrehozasa es inicializalasa
				int tmpTesztesetSzam = palya.getAktTeszteset();
				palya = new Palya();
				palya.init(tmpTesztesetSzam);
				// tile-ok referenciainak lekerese
				tilelista = palya.getTile();
				// jatekosok referenciainak lekerese
				jatekoslista = palya.getJatekos();
				kulso = false;
				tileAllapot = new ArrayList<ArrayList<String>>();
				j1Allapot = null;
				j2Allapot = null;
				allapotKiir(null);
				break;
			case exit:
				kulso = true;
				break;
			case output:
				if(com.getParam().equals("file")){
					fajlba = true;
					kepenyore = false;
				}
				else if(com.getParam().equals("both")){
					fajlba = true;
					kepenyore = true;
				}
				// csak scr lehet, parse fv-ben tesztelve
				else{
					fajlba = false;
					kepenyore = true;
				}
				if(com.getParam().equals("file") || com.getParam().equals("both")){
					System.out.println("Kerlek ird be az output fajl nevet");
					System.out.println("\t(ha letezik, a program hozzafuz, ha nem, letrehozza)");
					InputStreamReader isr = new InputStreamReader(System.in);
					BufferedReader br = new BufferedReader(isr);
					fajlnev = br.readLine();
				}
				break;
			case switchview:
				if(com.getParam().equals("j1")){
					// nezet negalasa (reszletesbol attekinto es forditva)
					jatekoslista.get(0).setReszletes(!jatekoslista.get(0).getReszletes());
				}
				// csak j2 lehet, parse fv-ben tesztelve
				else{
					// nezet negalasa (reszletesbol attekinto es forditva)
					jatekoslista.get(1).setReszletes(!jatekoslista.get(1).getReszletes());
				}
				break;
			default:
				break;
			}
		}
		// ha nem igaz, kiiratjuk az aktualis allapothoz (kulso/belso)
		// tartozo helpet
		else{
			
		}
	}
	
	public static void vegrehajt(Command c) throws Exception{
		// tick nelkul lefuto parancsok kezelese
		if(!c.getCommandElement().isBelso() ||
				c.getCommand()==CommandList.Commands.helpbelso ||
				c.getCommand()==CommandList.Commands.exit ||
				c.getCommand()==CommandList.Commands.reset){
			futtat(c);
		}
		// ha a kapott parancs tick n, akkor lefuttatjuk az elozo n parancsot
		// (vagy ha n > lista merete, akkor az osszeset)
		else if(c.getCommand()==CommandList.Commands.tick){
			Integer db = c.getNum();
			Command tmpc;
			if(!q.isEmpty()){
				int i;
				for (i = 0 ; (i < db)&&(!q.isEmpty()) ; i++) {
					if(palya.getVege()){
						c.setNum(0);
						break;
					}
					tmpc=q.poll();
					futtat(tmpc);
					if(!((tmpc.getCommand().equals(CommandList.Commands.movej1) ||
							tmpc.getCommand().equals(CommandList.Commands.movej2)) &&
							tmpc.getParam().equals("up"))){
						Billentyu bill = new Billentyu();
						palya.mozgatUgras(bill);
					}
					if(!((tmpc.getCommand().equals(CommandList.Commands.movej1) ||
							tmpc.getCommand().equals(CommandList.Commands.movej2)) &&
							(tmpc.getParam().equals("left") || tmpc.getParam().equals("right")))){
						Billentyu bill = new Billentyu();
						palya.mozgatLep(bill);
					}
					// utkozesvizsgalat
					palya.utkozesVizsgalat();
					// allapot kiiratasa
					allapotKiir(null);
				}
				if(i!=c.getNum()){
					int j = c.getNum()-i;
					Billentyu bill = new Billentyu();
					for(int k=0;k<j;k++){
						palya.mozgatUgras(bill);
						palya.mozgatLep(bill);
						palya.utkozesVizsgalat();
						// allapot kiiratasa
						allapotKiir(null);						
					}
				}
			}
			else{
				Billentyu bill = new Billentyu();
				for(int i=0;i<c.getNum();i++){
					palya.mozgatUgras(bill);
					palya.mozgatLep(bill);
					palya.utkozesVizsgalat();
					// allapot kiiratasa
					allapotKiir(null);
				}
			}
		}
		else{
			q.add(c);				
		}
	}
	
	// az osszes tile elozo allapota
	private static ArrayList<ArrayList<String>> tileAllapot = new ArrayList<ArrayList<String>>();
	// az egyik jatekos elozo allapota
	private static ArrayList<String> j1Allapot;
	// a masik jatekos elozo allapota
	private static ArrayList<String> j2Allapot;
	// az aktualis allapot kiiratasa
	public static void allapotKiir(ArrayList<String> kulcsAllapot){
		// kozmetika
		kiir("");
		
		// ha a kulcs hivja a fuggvenyt, csak ez az if fut le
		if(kulcsAllapot!=null){
			/************ KULCS ************/
			// a kulcs hivja ezt a fuggvenyt, csak akkor ha valtozott (felvettek)
			for(int i=0;i<kulcsAllapot.size();i++){
				kiir(kulcsAllapot.get(i));
			}
			kiir("");
			return;
		}
		
		/************ TILE-OK ************/
		// ha nem ures (nem ez az elso futasa a fv-nek)
		if(!tileAllapot.isEmpty()){
			// vegigmegyunk a listak listajan
			for(int i=0;i<tilelista.size()-1;i++){
				/*
				 * !!! FONTOS: a tilelistaban TILE-ok, a tileAllapotban aLLAPOTOK vannak !!!
				 * A tilelistabol kiszedjuk az i. tile allapotat (ArrayList<String>)
				 * es osszehasonlitjuk ugyanazon tile elozo allapotaval
				 * ha a lista ket eleme nem egyezik, kilepunk az aktualis for-bol...
				 */
				if(!tilelista.get(i).getAllapot().equals(tileAllapot.get(i))){
					// ...es osszehasonlitjuk elemenkent a ket nem egyezo listat
					ArrayList<String> tmpNew = tilelista.get(i).getAllapot();
					ArrayList<String> tmpOld = tileAllapot.get(i);
					kiir(tmpNew.get(0));
					for(int j=0;j<tmpNew.size();j++){
						if(!tmpNew.get(j).equals(tmpOld.get(j))){
							kiir(tmpNew.get(j));
						}
					}
					kiir("");
				}
				else{
					// ha egyezik, nev kiiratasa
					kiir(tilelista.get(i).getAllapot().get(0));
					kiir("\tNem valtozott semmi!\n");
				}
			}
			// a tileAllapot-ot feltoltjuk az aktualis allapotokkal
			tileAllapot = new ArrayList<ArrayList<String>>();
			for(int i=0;i<tilelista.size();i++){
				tileAllapot.add(tilelista.get(i).getAllapot());
			}
		}
		// ha ures, akkor ez az elso hivasa a fv-nek
		else{
			for(int i=0;i<tilelista.size()-1;i++){
				ArrayList<String> tmpList = tilelista.get(i).getAllapot();
				// hozzaadjuk az aktualis allapotlistat a listahoz
				tileAllapot.add(tmpList);
				for(int j=0;j<tmpList.size();j++){
					// valamint kiirjuk az aktualis allapotlista elemeit egyesevel
					kiir(tmpList.get(j));
				}
				kiir("");
			}
		}

		/************ JaTeKOS 1 ************/
		if(!(j1Allapot==null)){
			ArrayList<String> tmpNew = jatekoslista.get(0).getAllapot();
			// ha nem valtozott semmi az elozo ota
			if(tmpNew.equals(j1Allapot)){
				kiir(tmpNew.get(0));
				kiir("\tNem valtozott semmi!");
			}
			// ha valtozott
			else{
				kiir(tmpNew.get(0));
				for(int i=0;i<j1Allapot.size();i++){
					if(!j1Allapot.get(i).equals(tmpNew.get(i))){
						kiir(tmpNew.get(i));
					}
				}
			}
			kiir("");
			// uj allapotok beirasa
			j1Allapot = tmpNew;
		}
		// elso lefutaskor
		else{
			if (!(jatekoslista==null) && !jatekoslista.isEmpty()) {
				j1Allapot = jatekoslista.get(0).getAllapot();
				for (int i = 0; i < j1Allapot.size(); i++) {
					kiir(j1Allapot.get(i));
				}
				kiir("");
			}
		}
		
		/************ JaTeKOS 2 ************/
		if(!(j2Allapot==null)){
			ArrayList<String> tmpNew = jatekoslista.get(1).getAllapot();
			// ha nem valtozott semmi az elozo ota
			if(tmpNew.equals(j2Allapot)){
				kiir(tmpNew.get(0));
				kiir("\tNem valtozott semmi!");
			}
			// ha valtozott
			else{
				kiir(tmpNew.get(0));
				for(int i=0;i<j2Allapot.size();i++){
					if(!j2Allapot.get(i).equals(tmpNew.get(i))){
						kiir(tmpNew.get(i));
					}
				}
			}
			kiir("");
			// uj allapotok beirasa
			j2Allapot = tmpNew;
		}
		// elso lefutaskor
		else{
			// csak ha van 2. jatekos
			if (!(jatekoslista==null) && jatekoslista.size()>=2) {	
				j2Allapot = jatekoslista.get(1).getAllapot();
				for(int i=0;i<j2Allapot.size();i++){
					kiir(j2Allapot.get(i));
				}
				kiir("");
			}
		}
		
		kiir("********************************");
	}
	
	// kiiratas
	public static void kiir(String s){
		if(kepenyore){
			System.out.println(s);
		}
		if(fajlba){
			try{
				// Create file 
				File f;
				f=new File(fajlnev);
				if(!f.exists()){
					f.createNewFile();
				}
				FileWriter fstream = new FileWriter(f,true);
				BufferedWriter out = new BufferedWriter(fstream);
				out.newLine();
				out.write(s);
				//Close the output stream
				out.close();
			}catch (Exception e){//Catch exception if any
				  System.err.println("HIBA: irasi hiba! (" + e.getMessage()+")");
			}
		}
	}
	
}
